(function (window, undefined) {

    var BinaryReader = function (buffer) {

        this.dataView = new DataView(buffer);
        this.streamPosition = 0;
    };

    var exception = {
        readPastEnd: 0
    };

    var throwException = function (errorCode) {
        switch (errorCode) {
            case exception.readPastEnd:
                throw {
                    name: 'readPastEnd',
                    message: 'Read past the end of the file'
                };
                break;
        }
    };

    BinaryReader.prototype = {
        readUint8: function () { var result = this.dataView.getUint8(this.streamPosition, true); this._movePointerTo(this.streamPosition + 1); return result; },
        readInt8: function () { var result = this.dataView.getInt8(this.streamPosition, true); this._movePointerTo(this.streamPosition + 1); return result; },
        readUint16: function () { var result = this.dataView.getUint16(this.streamPosition, true); this._movePointerTo(this.streamPosition + 2); return result; },
        readInt16: function () { var result = this.dataView.getInt16(this.streamPosition, true); this._movePointerTo(this.streamPosition + 2); return result; },
        readUint32: function () { var result = this.dataView.getUint32(this.streamPosition, true); this._movePointerTo(this.streamPosition + 4); return result; },
        readInt32: function () { var result = this.dataView.getInt32(this.streamPosition, true); this._movePointerTo(this.streamPosition + 4); return result; },
        readFloat32: function () { var result = this.dataView.getFloat32(this.streamPosition, true); this._movePointerTo(this.streamPosition + 4); return result; },
        skipBytes: function (n) { this._movePointerTo(this.streamPosition + n); },
        seek: function (offset, origin) {

            switch (origin) {
                case seekOrigin.begin:
                    this._movePointerTo(offset);
                    break;
                case seekOrigin.current:
                    this._movePointerTo(this.streamPosition + offset);
                    break;
                case seekOrigin.end:
                    this._movePointerTo(this.dataView.byteLength + offset);
                    break;
            }
        },
        readString: function (numChars) {
            var chars = [];
            for (var i = 0; i < numChars; i++) {
                chars[i] = this.readUint8();
            }
            return binayUtf8ToString(chars, 0);
            // return String.fromCharCode.apply(null, chars);

        },
        _movePointerTo: function (offset) {
            if (offset < 0)
                this.streamPosition = 0;
            else if (offset > this.dataView.byteLength)
                throwException(exception.readPastEnd);
            else
                this.streamPosition = offset;
        }
    };

    BinaryReader.prototype.readByte = BinaryReader.prototype.readUint8;

    window.BinaryReader = BinaryReader;

    window.seekOrigin = {
        begin: 1,
        current: 2,
        end: 3
    };
})(window);

function binayUtf8ToString(buf, begin) {
    var i = 0;
    var pos = 0;
    var str = "";
    var unicode = 0;
    var flag = 0;
    for (pos = begin; pos < buf.length; ) {
        flag = buf[pos];
        if ((flag >>> 7) === 0) {
            str += String.fromCharCode(buf[pos]);
            pos += 1;

        }
        else if ((flag & 0xFC) === 0xFC) {
            unicode = (buf[pos] & 0x3) << 30;
            unicode |= (buf[pos + 1] & 0x3F) << 24;
            unicode |= (buf[pos + 2] & 0x3F) << 18;
            unicode |= (buf[pos + 3] & 0x3F) << 12;
            unicode |= (buf[pos + 4] & 0x3F) << 6;
            unicode |= (buf[pos + 5] & 0x3F);
            str += String.fromCharCode(unicode);
            pos += 6;

        } else if ((flag & 0xF8) === 0xF8) {
            unicode = (buf[pos] & 0x7) << 24;
            unicode |= (buf[pos + 1] & 0x3F) << 18;
            unicode |= (buf[pos + 2] & 0x3F) << 12;
            unicode |= (buf[pos + 3] & 0x3F) << 6;
            unicode |= (buf[pos + 4] & 0x3F);
            str += String.fromCharCode(unicode);
            pos += 5;

        }
        else if ((flag & 0xF0) === 0xF0) {
            unicode = (buf[pos] & 0xF) << 18;
            unicode |= (buf[pos + 1] & 0x3F) << 12;
            unicode |= (buf[pos + 2] & 0x3F) << 6;
            unicode |= (buf[pos + 3] & 0x3F);
            str += String.fromCharCode(unicode);
            pos += 4;

        }

        else if ((flag & 0xE0) === 0xE0) {
            unicode = (buf[pos] & 0x1F) << 12; ;
            unicode |= (buf[pos + 1] & 0x3F) << 6;
            unicode |= (buf[pos + 2] & 0x3F);
            str += String.fromCharCode(unicode);
            pos += 3;

        }
        else if ((flag & 0xC0) === 0xC0) { //110
            unicode = (buf[pos] & 0x3F) << 6;
            unicode |= (buf[pos + 1] & 0x3F);
            str += String.fromCharCode(unicode);
            pos += 2;

        }
        else {
            str += String.fromCharCode(buf[pos]);
            pos += 1;
        }
    }
    return str;

}

function readFileToArrayBuffer(file, successCallback,caller) {
    var reader = new FileReader();
    reader.onload = function (evt) {
        var buffer = reader.result;
       (caller == undefined || caller == null)?successCallback(buffer):successCallback.call(caller,buffer);

    };
    reader.onerror = function (evt) {
        if (evt.target.error.code == evt.target.error.NOT_READABLE_ERR) {
            alert("Failed to read file: " + file.name);
        }
    };
    try {
        reader.readAsArrayBuffer(file);
    }
    catch (e) {
        alert(e);
    }
}